package com.compunit.composite;

import java.util.Properties;
import java.util.concurrent.TimeUnit;

import javax.swing.JOptionPane;

import org.compunit.Provide;
import org.compunit.Require;
import org.compunit.interfaces.IComponent;
import org.compunit.interfaces.IResourceRetriever;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;

import com.compunit.composite.b.interfaces.IDriver;
import com.compunit.composite.b.interfaces.IRenderer;
import com.compunit.composite.b.interfaces.ITimer;
import com.compunit.composite.b.interfaces.IUpdater;

@Provide({IDriver.class})
@Require({ITimer.class, IUpdater.class, IRenderer.class})
public class Driver implements IDriver {
	
	private String title;
	private final Thread thread;
	private final long cyclelimit;
	
	private DisplayMode displayMode;
	
	private ITimer timer;
	private IUpdater updater;
	private IRenderer renderer;
	
	private volatile boolean finished;
	private long time;
	
	public Driver() {
		this.thread = new Thread(this);
		this.cyclelimit = 16;
	}

	@Override
	public void run() {
		this.initialize();
		this.renderer.initScene();
		GL11.glMatrixMode(GL11.GL_MODELVIEW);
		while(!this.finished) {
			this.timer.tick();
			final float interpolation = this.timer.getInterpolation();
			final long milli = TimeUnit.MILLISECONDS.convert((long)interpolation, this.timer.getUnit());
			this.updater.update(interpolation);
			if(Display.isActive()) {
				this.renderer.render();
			} else {
				if(Display.isVisible() || Display.isDirty()) {
					this.renderer.render();
				} else {
					try {TimeUnit.MILLISECONDS.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}
				}
			}
			this.time += milli;
			if(time >= 3000) {
				this.time = 0;
				System.out.println("Frame rate: ".concat(String.valueOf(this.timer.getTickRate())));
			}
			// Framerate.
			final long diff = this.cyclelimit - milli;
			if(diff > 0) try {TimeUnit.MILLISECONDS.sleep(diff);} catch (InterruptedException e) {e.printStackTrace();}
		}
		Display.destroy();
	}
	
	private void initialize() {
		try {
			this.selectMode();
			Display.setTitle(this.title);
			// Create display window.
			Display.create();
			// Enable 2D texture mode.
			GL11.glEnable(GL11.GL_TEXTURE_2D);
			// Set shading model to smooth.
	        GL11.glShadeModel(GL11.GL_SMOOTH);
	        // Background color to black.
	        GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	        // Clear the z-buffer.
	        GL11.glClearDepth(1.0);
	        // Enable z-buffer.
	        GL11.glEnable(GL11.GL_DEPTH_TEST);
	        // Set z-buffer test function to less than and equal to.
	        GL11.glDepthFunc(GL11.GL_LEQUAL);
	        // Set camera model to projection.
	        GL11.glMatrixMode(GL11.GL_PROJECTION);
	        // Reset transform matrix.
	        GL11.glLoadIdentity();
	        // Set camera perspective.
	        GLU.gluPerspective(45.0f, (float)this.displayMode.getWidth() / (float)this.displayMode.getHeight(), 0.1f, 100.0f);
	        // Set to use nice perspective projection.
	        GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST);
		} catch (LWJGLException e) {
			e.printStackTrace();
		}
	}
	
	private void selectMode() {
		try {
			Display.setFullscreen(false);
			DisplayMode modes[] = Display.getAvailableDisplayModes();
			this.displayMode = (DisplayMode)JOptionPane.showInputDialog(null, "Select display mode", "Selection",
					JOptionPane.PLAIN_MESSAGE, null, modes, modes[0]);
			if(this.displayMode == null) System.exit(0);
			Display.setDisplayMode(this.displayMode);
		} catch (LWJGLException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void customize(final Properties properties) throws Exception {
		this.title = properties.getProperty("title", "This can be customized.");
	}

	@Override
	public boolean activate(final IResourceRetriever handler) throws Exception {
		if(this.timer == null) throw new IllegalArgumentException("No available timer.");
		else if(this.updater == null) throw new IllegalArgumentException("No available updater");
		else if(this.renderer == null) throw new IllegalArgumentException("No available renderer");
		this.finished = false;
		this.thread.start();
		return true;
	}

	@Override
	public boolean connect(final IComponent unit, final String interfaceName) throws Exception {
		if(unit instanceof ITimer) {
			this.timer = (ITimer)unit;
			return true;
		} else if(unit instanceof IUpdater) {
			this.updater = (IUpdater)unit;
			return true;
		} else if(unit instanceof IRenderer) {
			this.renderer = (IRenderer)unit;
			return true;
		}
		return false;
	}

	@Override
	public void deactivate() throws Exception {
		this.finished = true;
	}
}
